package archer.framework.file.repository.impl;

import archer.framework.file.config.FileRepositoryConfig;
import archer.framework.file.entity.FileResource;
import archer.framework.file.entity.Metadata;
import archer.framework.file.naming.impl.MD5NamingScheme;
import archer.framework.file.naming.NamingScheme;
import archer.framework.file.repository.Repository;
import archer.framework.file.repository.RepositoryStorageException;
import archer.framework.utils.MimeUtils;
import eu.medsea.mimeutil.MimeUtil2;
import jodd.io.FileUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.activation.MimetypesFileTypeMap;
import java.io.*;

/**
 * 文件系统资源仓库
 *
 * @author christ
 * @date 2016/7/4
 */
@Component
public class FileSystemRepository implements Repository {

    private static final Logger logger = LoggerFactory.getLogger(FileSystemRepository.class);

    @Autowired
    protected FileRepositoryConfig config;

    protected NamingScheme namingScheme = new MD5NamingScheme();

    @Override
    public Metadata addPublicFileResource(FileResource file, String subDir) throws RepositoryStorageException {

        return addFileResource(file, subDir, true);
    }

    @Override
    public Metadata addPrivateFileResource(FileResource file, String subDir) throws RepositoryStorageException {

        return addFileResource(file, subDir, false);
    }

    protected Metadata addFileResource(FileResource file, String subDir, boolean isPub) throws RepositoryStorageException {

        // 文件校验
        check(file);

        String dir = getDir(subDir, isPub);

        assertDirExists(dir);

        String fileName = genFileName(file);

        try {

            // 写出文件
            writeFileResourceToDisk(file, dir, fileName);

            // 设置文件id
            file.getMetadata().setId(getFileId(fileName, isPub));

            if (file.getMetadata().getMimeType() == null) {
                file.getMetadata().setMimeType(new MimetypesFileTypeMap().getContentType(fileName));
            }

            if (config.isDetectMimeType() && MimeUtil2.UNKNOWN_MIME_TYPE.equals(file.getMetadata().getMimeType())) {
                file.getMetadata().setMimeType(MimeUtils.getMimeType(file.getData()));
            }

            // 写出元数据
            writeMetadataToDisk(file, dir, fileName);

            return file.getMetadata();

        } catch (Exception e) {

            logger.error(e.getMessage(), e);
            throw new RepositoryStorageException("上传文件失败：" + file.getMetadata().getFilename(), e);
        }
    }

    /**
     * 获得文件存储目录
     *
     * @param isPub
     * @return
     */
    protected String getDir(String subDir, boolean isPub) {

        String baseDir = config.getBaseDir() + (isPub ? config.getPublicDir() : config.getPrivateDir());
        return subDir == null ? baseDir : baseDir + subDir + "/";
    }

    /**
     * 生成文件名
     *
     * @param file
     * @return
     */
    protected String genFileName(FileResource file) {

        return namingScheme.newFile(file.getData()) + getExtension(file.getMetadata().getFilename());
    }

    /**
     * 获得文件id
     *
     * @param fileName
     * @param isPub
     * @return
     */
    protected String getFileId(String fileName, boolean isPub) {

        return (isPub ? config.getPublicPrefix() : config.getPrivatePrefix()) + fileName;
    }

    /**
     * 文件校验
     *
     * @param file
     */
    protected void check(FileResource file) {

        if (file == null || file.getData() == null || file.getData().length <= 0) {
            throw new RepositoryStorageException("文件不能为空");
        }

        if (file.getData().length > config.getMaxSize()) {
            throw new RepositoryStorageException("文件大小超过限制：" + file.getMetadata().getFilename());
        }
    }

    /**
     * 确保文件目录正确性
     *
     * @param dirPath
     */
    protected void assertDirExists(String dirPath) {
        File dir = new File(dirPath);
        if (dir.exists() != true) {
            dir.mkdirs();
        }
    }

    /**
     * 获得文件后缀
     *
     * @param filename
     * @return
     */
    private String getExtension(String filename) {
        int dot = filename.lastIndexOf(".");
        if (dot < 0) {
            return "";
        } else {
            String extension = filename.substring(dot + 1);
            if (extension.length() == 0) {
                return "";
            } else {
                return "." + extension;
            }
        }
    }

    /**
     * 写出文件资源
     *
     * @param fileResource
     * @param dirPath
     * @param id
     * @throws IOException
     */
    protected void writeFileResourceToDisk(FileResource fileResource, String dirPath, String id) throws IOException {

        File file = new File(dirPath + id);
        file.createNewFile();
        OutputStream output = null;
        try {

            output = new FileOutputStream(file);

            logger.info("Writing " + fileResource.getData().length + " bytes to " + file.getAbsoluteFile());

            output.write(fileResource.getData());

            // relative path
            String path = file.getPath();
            path = "/" + path.replaceAll("\\\\", "/")
                    .replaceAll(config.getBaseDir() + config.getPrivateDir(), "")
                    .replaceAll(config.getBaseDir() + config.getPublicDir(), "");

            fileResource.getMetadata().setPath(path);

        } catch (FileNotFoundException e) {
            logger.error(e.getMessage(), e);
            throw new IOException("File " + file.getAbsoluteFile() + " was expected to exist already.");

        } finally {
            if (output != null) {
                output.close();
            }
        }
    }

    /**
     * 写出元数据资源
     *
     * @param fileResource
     * @param dirPath
     * @param id
     * @throws IOException
     */
    protected void writeMetadataToDisk(FileResource fileResource, String dirPath, String id) throws IOException {

        File file = new File(dirPath + id + ".metadata");
        file.createNewFile();
        OutputStream output = null;
        try {
            output = new FileOutputStream(file);
            output.write(fileResource.getMetadata().toString().getBytes());
            output.flush();
        } catch (FileNotFoundException e) {
            logger.error(e.getMessage(), e);
            throw new IOException("File " + file.getAbsoluteFile() + " was expected to exist already.");
        } finally {
            if (output != null) {
                output.close();
            }
        }
    }

    @Override
    public FileResource getFileResource(String subDir, String id) throws RepositoryStorageException {

        byte[] data;
        try {
            data = FileUtil.readBytes(resolveFileDir(subDir, id) + resolveFileName(id));
        } catch (IOException e) {
            logger.error("Could not get file for id:" + id, e);
            throw new RepositoryStorageException("找不到文件：" + id, e);
        }
        return new FileResource(id, data, null).setMetaData(getMetadata(subDir, id));
    }

    @Override
    public Metadata getMetadata(String subDir, String id) throws RepositoryStorageException {

        byte[] data;
        try {
            data = FileUtil.readBytes(resolveFileDir(subDir, id) + resolveFileName(id) + ".metadata");
        } catch (IOException e) {
            logger.error("Could not get file for id:" + id, e);
            throw new RepositoryStorageException("Could not get file for id:" + id, e);
        }
        return (new Metadata()).load(new String(data));
    }

    /**
     * 根据文件id获得真实的文件名
     *
     * @param id
     * @return
     */
    protected String resolveFileName(String id) {

        if (id.startsWith(config.getPrivatePrefix())) {

            return id.replaceFirst(config.getPrivatePrefix(), "");
        }

        return id.replaceFirst(config.getPublicPrefix(), "");
    }

    /**
     * 根据文件id解析出文件目录
     *
     * @param id
     * @return
     * @throws FileNotFoundException
     */
    protected String resolveFileDir(String subDir, String id) throws FileNotFoundException {

        if (id.startsWith(config.getPublicPrefix())) {

            return getDir(subDir, true);
        }

        if (id.startsWith(config.getPrivatePrefix())) {

            return getDir(subDir, false);
        }

        throw new FileNotFoundException(id);
    }

    @Override
    public void deleteFileResource(String subDir, String id) throws RepositoryStorageException {

        OutputStream os = null;
        try {

            Metadata metadata = getMetadata(subDir, id);

            // 非逻辑删除的情况下直接删除文件
            if (!config.isLogicalDelete()) {
                File f = new File(resolveFileDir(subDir, id) + resolveFileName(id));
                f.delete();
                metadata.setStatus("DELETED");
            } else {
                metadata.setStatus("LOGICAL_DELETED");
            }

            File meta = new File(resolveFileDir(subDir, id) + resolveFileName(id) + ".metadata");
            os = new FileOutputStream(meta);
            os.write(metadata.toString().getBytes());
        } catch (FileNotFoundException e) {
            logger.error("Could not get file for id:" + id, e);
            throw new RepositoryStorageException("找不到文件：" + id, e);
        } catch (IOException e) {
            logger.error("Could not get file for id:" + id, e);
            throw new RepositoryStorageException("找不到文件：" + id, e);
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public FileRepositoryConfig getConfig() {
        return config;
    }

    public void setConfig(FileRepositoryConfig config) {
        this.config = config;
    }

    public NamingScheme getNamingScheme() {
        return namingScheme;
    }

    public void setNamingScheme(NamingScheme namingScheme) {
        this.namingScheme = namingScheme;
    }
}
